Figure guide
A practical guide for making scientific figures. See also Esteban Moro’s figure guide and Nature’s research figure guide.
A paper is essentially a sequence of figures. Many scientists “read” a paper by (1) skimming the abstract, then (2) looking at the figures and captions. If the figures convey the main results clearly, readers are more likely to read the full text. Think of 4–5 key messages, not individual plots. Each figure should communicate a message, supported by several panels. Consider a figure not as a plot, but as a message.
Making a good figure is a hierarchy of priorities:
- Visible and readable — Can the reader actually see and read everything?
- Effective and informative — Does the figure convey the right message clearly and efficiently?
- Engaging — Does it draw the reader in and reward their attention?
Most figure problems live at level 1. Get that right first.
Bitmap vs. vector
If your scientific figures are bitmaps, something has likely gone wrong. You should feel a visceral, physical pain when you see a pixelated plot in a paper—jagged edges on lines, fuzzy text on axis labels. You should develop the taste to fill this pain. It means the figure was created (or saved, or converted) in a way that destroyed information.
Vector graphics (PDF, SVG, EPS) store shapes, lines, and text as mathematical descriptions. They scale to any size with no loss of quality. This is what we want for plots, diagrams, and anything with text or lines.
Bitmap (raster) graphics (PNG, JPG, TIFF) store a grid of pixels. They have a fixed resolution. Zoom in and you see squares. Shrink them and you lose detail. They are appropriate for photographs, microscopy images, heatmaps of large matrices—cases where the data is inherently pixel-based.
The rule is simple: never produce bitmap figures. If your figure was generated from data or drawn from geometric primitives, it must be vector. Full stop. The only exception is when the data is inherently raster (photographs, microscopy)—and even then, the axes, labels, and annotations should remain vector. If a vector file is too large because of millions of scatter points or a dense heatmap, rasterize just that layer and keep everything else as vectors. Reaching for PNG or JPG should feel like a last resort, not a default.
Saving vector output
plt.savefig('plot.pdf') # vector — always preferred
ggsave("plot.pdf", p, width = 89, height = 60, units = "mm")
When you must use raster
Sometimes you have no choice—photographs, microscopy, screenshots. In that case:
- 300 DPI minimum for publication (600+ for line art in raster)
- Use PNG (lossless) for plots, never JPG (lossy compression adds artifacts around sharp edges and text)
- Set the correct physical dimensions so the DPI is meaningful:
pixels = inches × DPI
plt.savefig('plot.png', dpi=300) # 300 DPI minimum
ggsave("plot.png", p, width = 89, height = 60, units = "mm", dpi = 300)
Common DPI values:
- 72–96: screen display
- 150: draft printing
- 300: publication quality
- 600+: professional printing
Common traps
- PowerPoint and Google Slides paste vector graphics as bitmaps by default. If you drag-and-drop a PDF, it often gets rasterized. Use “Insert > Image” or paste as EMF/SVG where possible.
- Screenshots of plots are bitmaps, obviously. Never screenshot a plot from a Jupyter notebook or R Studio and put that in a paper.
- Format conversion can silently rasterize. Converting PDF → PNG → PDF does not give you a vector file back—it gives you a bitmap wrapped in a PDF container. The damage is done.
1. Visible and readable
Right-sizing: the most common mistake
Most people don’t realize that figures have physical dimensions. On a screen you can zoom freely, but on paper, size is fixed — and font sizes mean something (in points, on paper).
Know your target size. Common journal widths:
| Type | Width |
|---|---|
| Single column | 89 mm (3.5”) |
| Double column | 183 mm (7.2”) |
| US Letter content area | 6.5” × 9” |
If you create a figure at matplotlib’s default size (6.4” wide) and it gets shrunk to fit a 3.5” column, text shrinks by roughly half — 10 pt becomes ~5 pt and is illegible. Often your figure is just a small panel within a larger figure, so the shrinkage is even worse.
The fix: set the correct physical size from the start.
Python (matplotlib)
# Wrong: create large, then shrink
fig, ax = plt.subplots(figsize=(14, 8)) # way too big
ax.set_xlabel('Time (s)', fontsize=7) # fontsize is meaningless at this scale
# Right: create at target size
fig, ax = plt.subplots(figsize=(3.5, 2.5)) # single-column width
ax.set_xlabel('Time (s)', fontsize=7) # 7 pt on paper
plt.savefig('plot.pdf') # vector format preferred
Set defaults so every figure starts at the right size:
plt.rcParams.update({
'figure.figsize': (3.5, 2.5),
'font.size': 10,
'axes.labelsize': 10,
'axes.titlesize': 10,
'xtick.labelsize': 8,
'ytick.labelsize': 8,
})
R (ggplot2)
ggsave("plot.pdf", p, width = 89, height = 60, units = "mm")
R’s ggsave supports units directly ("in", "cm", "mm", "px"). Default is 7 × 7 inches — quite large.
Design tools (Illustrator, etc.)
Same principle: set the correct physical size first. Don’t design at arbitrary pixel dimensions and scale later.
2. Effective and informative
TODO
3. Engaging
TODO
Checklist
- Know your target size (check journal guidelines)
- Set figsize in physical dimensions from the start
- Use appropriate font sizes (5–7 pt for Nature, 7–10 pt for most journals)
- Save as vector (PDF, SVG) when possible
- If raster, save at 300 DPI minimum
- Never scale down large figures — create at the right size initially